<?php

/**
 * use two step structure to store template
 * step 1. template: name, [header: name, footer:name, content: name]
 * step 2. header: name, elements: []
 * step 2. footer: name, elements: []
 * step 2. content: name, elements: []
 */
class Template
{

	/**
	 * @var Template
	 */
	static private $_initiator = null;

	/**
	 * @var array
	 */
	protected $_headVars = array();

	/**
	 * @var string
	 */
	protected $_theme = 'default';

	/**
	 * @var string
	 */
	protected $_name = 'default';

	/**
	 * @var string
	 */
	protected $_variation = null;

	/**
	 * @var array
	 */
	public $jsConfig = array();

	/**
	 * @param string $name
	 * @return Template
	 */
	public function setName($name)
	{
		$this -> _name = $name;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getName()
	{
		return $this -> _name;
	}

	/**
	 * @param string $variatin
	 * @return Template
	 */
	public function setVariation($variation)
	{
		$this -> _variation = $variation;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getVariation()
	{
		return $this -> _variation;
	}

	/**
	 * @return string
	 */
	public function getTheme()
	{
		return $this -> _theme;
	}

	/**
	 * @param string $theme
	 * @return Template
	 */
	public function setTheme($theme)
	{
		$this -> _theme = $theme;
		return $this;
	}

	public static function initiator()
	{
		if (null == self::$_initiator)
		{
			self::$_initiator = new self;
		}
		return self::$_initiator;
	}

	// execute a template name
	public function execute()
	{

		$layouts = array();

		$layoutConfig = array(
			'header',
			'content',
			'footer'
		);

		foreach ($layoutConfig as $layoutName)
		{
			$layouts[$layoutName] = $this -> _executeLayout($layoutName, $this -> _name);
		}

		$xhtml = implode('<div class="clear-fix"></div>', $layouts);

		$page = '<html>';
		$page .= '<head>';

		$page .= $this -> _executeHead();

		$page .= '</head>';
		$page .= '<body><div id="site">' . $xhtml . '</div></body>';
		$page .= '</html>';
		return $page;

	}

	protected function _executeLayout($layoutName, $layoutId)
	{
		$xhtml = array();

		$layout = $this -> loadLayout($layoutName, $layoutId);

		$locations = $layout['locations'];

		//each region need to work as action and some thing else.

		foreach ($locations as $location => $elements)
		{
			$xhtml[$location] = $this -> _executeLocation($location, $elements);
		}

		$render = isset($layout['layout']) ? $layout['layout'] : 'layout1';

		$render = 'Render_' . ucfirst($render);

		$render = new $render;

		$xhtml = $render -> execute($xhtml);

		$xhtml = '<div class="lay-' . $layoutName . '">' . $xhtml . '</div>';

		return $xhtml;
	}

	public function _executeLocation($location, $elements)
	{
		$xhtml = '';

		foreach ($elements as $element)
		{
			// execute each region to make sure.
			try
			{

				$path = isset($element['path']) ? $element['path'] : null;

				if (null == $path)
				{
					continue;
				}

				$params = $element['params'];

				$cls = str_replace('/', '-', $path);

				$request = Request::factory($path, $params);

				// SKIP THIS REQUEST
				if ($request -> execute($initial = FALSE) === FALSE)
				{
					continue;
				}

				$xhtml .= '<div class="' . $cls . '">' . $request -> response . '</div>';
				
				unset($request);

			}
			catch(Exception $e)
			{
				Logger::instance() -> write($e -> getMessage());
				if (APP_ENV == 'development')
				{
					echo $e -> getMessage();
				}
			}
		}
		return '<div class="loc-' . $location . '">' . $xhtml . '</div>';
	}

	/**
	 * load layout by name and id.
	 * @param string $layoutName   Is used for default layout id value.
	 * @param string/id $layoutId  Is used to search layout configure in a
	 * MongoDb Table when has a custom configured.
	 */

	/*
	 public function loadLayout($layout, $name)
	 {
	 if ($name == null)
	 {
	 $name = $this -> name;
	 }

	 if (null == $name)
	 {
	 $name = Request::initiator() -> getPath();
	 }

	 $id = sprintf('%s-%s', $layout, $name);

	 $db = Mongo_Database::instance() -> selectCollection('layouts');

	 $result = $db -> findOne(array('_id' => $id));

	 if (null == $result)
	 {
	 $id = sprintf('%s-%s', $layout, 'default');
	 $result = $db -> findOne(array('_id' => $id));
	 }

	 if (null == $result)
	 {

	 $id = sprintf('%s-%s', $layout, 'default');

	 $result = $db -> findOne(array('_id' => $id));
	 }

	 if (null == $result)
	 {
	 switch($layout) {
	 case 'header' :
	 case 'footer' :
	 $result = array('locations' => array());
	 break;
	 case 'content' :
	 $result = array('locations' => array('content' => array( array(
	 'action' => 'block/content',
	 'params' => array()
	 ))));
	 }
	 }
	 return $result;
	 }
	 * */

	/**
	 * load layout by name and id.
	 * @param string $layoutName   Is used for default layout id value.
	 * @param string/id $layoutId  Is used to search layout configure in a
	 * MongoDb Table when has a custom configured.
	 */
	public function loadLayout($layout, $name)
	{
		if ($name == null)
		{
			$name = $this -> _name;
		}

		if (null == $name)
		{
			$name = Request::initiator() -> getPath();
		}

		$id = sprintf('%s-%s', $layout, $name);

		$result = Agilet::loadConfig('layout/' . $id);

		if (null == $result)
		{
			$id = sprintf('%s-%s', $layout, 'default');
			$result = Agilet::loadConfig('layout/' . $id);
		}

		return $result;
	}

	/**
	 * @return string
	 */
	public function _executeHead()
	{

		$this -> _config = Agilet::getSetting('general');

		$theme = $this -> _theme;

		$variation = $this -> _variation;

		$staticBaseUrl = Agilet::getStaticBaseUrl();

		/**
		 * process some thing else as content or not of page.
		 */
		$this -> addMeta(array(
			'http-equiv' => 'content-type',
			'content' => 'text/html; charset=utf-8'
		)) -> addMeta(array(
			'http-equiv' => 'content-language',
			'content' => 'en-us',
		)) -> addMeta(array(
			'name' => 'revisit-after',
			'content' => $this -> getConfig('revisit-after', '1 days'),
		)) -> addMeta(array(
			'name' => 'robots',
			'content' => $this -> getConfig('robots', 'robots.txt')
		)) -> addMeta(array(
			'name' => 'owner',
			'content' => $this -> getConfig('owner')
		)) -> addMeta(array(
			'name' => 'author',
			'content' => $this -> getConfig('author')
		)) -> addMeta(array(
			'name' => 'generator',
			'content' => $this -> getConfig('generator')
		)) -> addLink(array(
			'rel' => 'shortcut icon',
			'type' => 'image/x-icon',
			'href' => $staticBaseUrl . '/favicon.ico'
		));

		$this -> prependCssLink($staticBaseUrl . 'theme/' . $theme . '/styles/all.css');
		// add main js
		$this -> prependJsLink($staticBaseUrl . 'script/main.js');
		$this -> prependJsLink($staticBaseUrl . 'script/jquery-1.7.2.min.js');

		/**
		 * @var array
		 */
		$allows = array(
			'Base',
			'Link',
			'CssLink',
			'Meta',
			'Keyword',
			'Description',
			'Title',
			'Css',
			'JsConfig',
			'JsLink',
			'Script'
		);

		/**
		 * @var array
		 */
		$xhtml = array();

		foreach ($allows as $allow)
		{
			if (($html = $this -> {'_renderHead' . $allow}()) != '')
			{
				$xhtml[] = $html;
			}
		}
		return implode(PHP_EOL, $xhtml);
	}

	public function addCssLink($value)
	{
		return $this -> __processHead('add', 'CssLink', $value);
	}

	public function appendCssLink($value)
	{
		return $this -> __processHead('append', 'CssLink', $value);
	}

	public function prependCssLink($value)
	{
		return $this -> __processHead('prepend', 'CssLink', $value);
	}

	public function addJsLink($value)
	{
		return $this -> __processHead('add', 'JsLink', $value);
	}

	public function appendJsLink($value)
	{
		return $this -> __processHead('append', 'JsLink', $value);
	}

	public function prependJsLink($value)
	{
		return $this -> __processHead('prepend', 'JsLink', $value);
	}

	public function addLink($value)
	{
		return $this -> __processHead('add', 'Link', $value);
	}

	public function appendLink($value)
	{
		return $this -> __processHead('append', 'Link', $value);
	}

	public function prependLink($value)
	{
		return $this -> __processHead('prepend', 'Link', $value);
	}

	public function addScript($value)
	{
		return $this -> __processHead('add', 'Script', $value);
	}

	public function appendScript($value)
	{
		return $this -> __processHead('append', 'Script', $value);
	}

	public function prependScript($value)
	{
		return $this -> __processHead('prepend', 'Script', $value);
	}

	public function addMeta($value)
	{
		$this -> _headVars['Meta'][] = $value;
		return $this;
	}

	public function appendMeta($value)
	{
		return $this -> __processHead('append', 'Meta', $value);
	}

	public function prependMeta($value)
	{
		return $this -> __processHead('prepend', 'Meta', $value);
	}

	public function addTitle($value)
	{
		return $this -> __processHead('add', 'Title', $value);
	}

	public function appendTitle($value)
	{
		return $this -> __processHead('append', 'Title', $value);
	}

	public function prependTitle($value)
	{
		return $this -> __processHead('prepend', 'Title', $value);
	}

	public function addKeyword($value)
	{
		return $this -> __processHead('add', 'Keyword', $value);
	}

	public function appendKeyword($value)
	{
		return $this -> __processHead('append', 'Keyword', $value);
	}

	public function prependKeyword($value)
	{
		return $this -> __processHead('prepend', 'Keyword', $value);
	}

	public function addDescription($value)
	{
		return $this -> __processHead('add', 'Description', $value);
	}

	public function appendDescription($value)
	{
		return $this -> __processHead('append', 'Description', $value);
	}

	public function prependDescription($value)
	{
		return $this -> __processHead('prepend', 'Description', $value);
	}

	/**
	 * placement
	 * @param string $name
	 * @param string/array $value
	 * @return mixed
	 */
	protected function __processHead($action, $key, $value = null)
	{

		if (!isset($this -> _headVars[$key]))
		{
			$this -> _headVars[$key] = array();
		}

		if (method_exists($this, $method = '_correct' . $key))
		{
			$value = $this -> {$method}($value);
		}

		switch($action)
		{
			case 'append' :
			case 'add' :
				$this -> _headVars[$key][] = $value;
				return $this;
			case 'prepend' :
				$this -> _headVars[$key] = array_reverse($this -> _headVars[$key]);
				$this -> _headVars[$key][] = $value;
				$this -> _headVars[$key] = array_reverse($this -> _headVars[$key]);
				return $this;
			case 'clear' :
				$this -> _headVars[$key] = array();
				return $this;
			case 'set' :
				$this -> _headVars[$key] = array($value);
			case 'has' :
				return isset($this -> _headVars[$key]) && !empty($this -> _headVars[$key]);
			case 'none' :
		}
		return $this;
	}

	/**
	 * match action
	 * @param string $name
	 * @param string $option
	 * @param string $default
	 */
	protected function __empty($name, $option, $default = '')
	{

		$value = $this -> getConfig($option, $default = '');

		if (isset($this -> _vars[$name]) && !empty($this -> _vars[$name]))
		{
			$this -> process($this -> getConfig($option . '-option', ' append'), $name, $value);
		}
		else
		{
			$this -> process('set', $name, $value);
		}
		return $this;
	}

	static protected function __flat($array)
	{
		$xhtml = array();
		if (is_array($array))
		{
			foreach ($array as $key => $value)
			{
				$xhtml[] = sprintf('%s="%s"', $key, $value);
			}
		}

		return implode(' ', $xhtml);
	}

	/**
	 * get html from current tile
	 * @return
	 */
	protected function _renderHeadTitle()
	{
		$vars = $this -> getHeadVar('Title');

		$sep = $this -> getConfig('title-separate', '-');

		if (empty($vars))
		{
			$vars = array($this -> getConfig('title'));
		}

		return sprintf('<title>%s</title>', implode($sep, $vars));
	}

	/**
	 * @return array
	 */
	protected function _renderHeadJsConfig()
	{
		$this -> jsConfig['basePath'] = Agilet::getAppBasePath();
		$this -> jsConfig['staticUrl'] = Agilet::getStaticBaseUrl();
		$js = json_encode($this -> jsConfig);
		return '<script type="text/javascript">var _config =' . $js . ';</script>';
	}

	/**
	 * flatten meta to html
	 * @return string
	 */
	protected function _renderHeadMeta()
	{
		$xhtml = array();

		foreach ($this->getHeadVar('Meta') as $meta)
		{
			$xhtml[] = '<meta ' . $this -> __flat($meta) . '/>';
		}
		return implode(PHP_EOL, $xhtml);
	}

	/**
	 * flatten keyword to html
	 * @return string
	 */
	protected function _renderHeadKeyword()
	{
		$vars = $this -> getHeadVar('Keyword');
		if (empty($keyword))
		{
			$vars = array($this -> getConfig('keyword'));
		}
		return '<meta name="keyword" content="' . implode(', ', $vars) . '"/>';
	}

	/**
	 * flatten description to html
	 * @return string
	 */
	protected function _renderHeadDescription()
	{
		$vars = $this -> getHeadVar('Description');
		if (empty($keyword))
		{
			$vars = array($this -> getConfig('description'));
		}
		return '<meta name="description" content="' . implode(', ', $vars) . '"/>';
	}

	/**
	 * flatten link to html
	 * @return string
	 */
	protected function _renderHeadLink()
	{
		$xhtml = array();

		foreach ($this->getHeadVar('Link') as $value)
		{
			$xhtml[] = '<link ' . $this -> __flat($value) . '/>';
		}
		return implode(PHP_EOL, $xhtml);
	}

	/**
	 * flatten css link to html
	 * @return string
	 */
	protected function _renderHeadCssLink()
	{
		$xhtml = array();

		foreach ($this->getHeadVar('CssLink') as $value)
		{
			$xhtml[] = '<link ' . $this -> __flat($value) . '/>';
		}
		return implode(PHP_EOL, $xhtml);
	}

	/**
	 * flatten js link to html
	 * @return string
	 */
	protected function _renderHeadJsLink()
	{
		$xhtml = array();
		foreach ($this->getHeadVar('JsLink') as $value)
		{
			$xhtml[] = '<script ' . $this -> __flat($value) . '></script>';
		}
		return implode(PHP_EOL, $xhtml);
	}

	/**
	 * get base href tags
	 * @return string
	 */
	protected function _renderHeadBase()
	{
		return '<base href="' . Agilet::getUrl('/') . '"/>';
	}

	/**
	 * flatten opengrap to meta, it is helpful for social sites to know what is
	 * this
	 * site
	 * @return string
	 */
	protected function _renderHeadOpengraph()
	{
		return '';
	}

	protected function _renderHeadScript()
	{
		$script = implode(';' . PHP_EOL, $this -> getHeadVar('Script'));

		if ($script)
		{
			return '<script type="text/javascript">' . $script . '</script>';
		}

		return '';
	}

	protected function _renderHeadCss()
	{
		$style = implode(';' . PHP_EOL, $this -> getHeadVar('Css'));

		if ($style)
		{
			return '<style type="text/css">' . $style . '</style>';
		}

		return '';
	}

	protected function _correctCssLink($value)
	{
		if (is_string($value))
		{
			$value = array('href' => $value);
		}
		return array_merge(array(
			'type' => 'text/css',
			'rel' => 'stylesheet',
			'media' => 'all'
		), $value);
	}

	protected function _correctJsLink($value)
	{
		if (is_string($value))
		{
			$value = array('src' => $value);
		}
		return array_merge(array('type' => 'text/javascript'), $value);
	}/**

	 * @param string $name
	 * @param mixed $default
	 * @return mixed
	 */
	public function getConfig($key, $default = null)
	{
		return isset($this -> _config[$key]) ? $this -> _config[$key] : $default;
	}

	/**
	 * @param array $options
	 * @return
	 */
	public function setConfig($key, $value)
	{
		$this -> _config[$key] = $value;
		return $this;
	}

	/**
	 * @param string $name
	 * @return array
	 */
	public function getHeadVar($key)
	{
		return isset($this -> _headVars[$key]) ? $this -> _headVars[$key] : array();
	}

}
